/*
 * Copyright (c) 2009 Robert Esser
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.e2ser.util;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import net.e2ser.PNBrowser;
import net.e2ser.component.AbstractComponent;
import net.e2ser.component.Place;
import net.e2ser.component.RootNode;
import net.e2ser.component.Transition;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import android.util.Log;

/**
 * 
 * @author Rob Esser
 *
 */
public class XMLReader extends DefaultHandler {

  // Used to define what elements we are currently in
  private boolean inTop = false;
  private boolean inNodes = false;
  private boolean inArcs = false;

  private InputStream is;
  private RootNode top = null;
  private HashMap<String, NodeProxy> nodeMap = new HashMap<String, NodeProxy>();
  private HashMap<String, ArcProxy> arcMap = new HashMap<String, ArcProxy>();

  public XMLReader(InputStream is) {
    this.is = is;
  }

  public void close() throws IOException {
    is.close();
  }

  public void startElement(String uri, String name, String qName, Attributes atts) {
    //Log.v(PNBrowser.TAG, "startElement: " + name + ", qName: " + qName);
    if (name.trim().equals("top")) {
      inTop = true;
      processTopAttributes(atts);
    } else if (name.trim().equals("nodes")) {
      inNodes = true;
    } else if (name.trim().equals("arcs")) {
      inArcs = true;
    } else if (name.trim().equals("ref")) {
      //process ref
      processRefAttributes(atts);
    } else if (name.trim().equals("trans")) {
      //process trans
      processtransAttributes(atts);
    } else if (name.trim().equals("place")) {
      //process place
      processPlaceAttributes(atts);
    } else if (name.trim().equals("arc")) {
      //process arc
      processArcAttributes(atts);
    }
  }

  private void processArcAttributes(Attributes atts) {
    assert inTop;
    assert !(inNodes || inArcs);
    String id = null, n = null, to = null, from = null;
    for (int i = 0; i < atts.getLength(); i++) {
      //Log.v(PNBrowser.TAG, "arc attr: " + atts.getLocalName(i) + " = " + atts.getValue(i) + " : " + atts.getType(i));
      String name = atts.getLocalName(i).trim();
      String val = atts.getValue(i).trim();
      if (name.equals("id")) {
        id = val;
      } else if (name.equals("n")) {
        n = val;
      } else if (name.equals("to")) {
        to = val;
      } else if (name.equals("from")) {
        from = val;
      } else {
        Log.v(PNBrowser.TAG, "ERROR: unknown place attribute: " + name + "=" + atts.getValue(i));
      }
    }
    //now add attributes in the proxy
    ArcProxy proxy = arcMap.get(id);
    if (proxy == null) {
      Log.v(PNBrowser.TAG, "ERROR: arc with id=" + id + " has no reference" );
      return;
    }
    proxy.toId = to;
    proxy.fromId = from;
    proxy.name = n;
  }

  private void processPlaceAttributes(Attributes atts) {
    assert inTop;
    assert !(inNodes || inArcs);
    String id = null;
    int l = 0, r = 0, t = 0, b = 0, tok = 0;
    String n = null;
    for (int i = 0; i < atts.getLength(); i++) {
      //Log.v(PNBrowser.TAG, "place attr: " + atts.getLocalName(i) + " = " + atts.getValue(i) + " : " + atts.getType(i));
      String name = atts.getLocalName(i).trim();
      String val = atts.getValue(i).trim();
      if (name.equals("id")) {
        id = val;
      } else if (name.equals("n")) {
        n = val;
      } else if (name.equals("l")) {
        l = Integer.parseInt(val);
      } else if (name.equals("r")) {
        r = Integer.parseInt(val);
      } else if (name.equals("t")) {
        t = Integer.parseInt(val);
      } else if (name.equals("b")) {
        b = Integer.parseInt(val);
      } else if (name.equals("tok")) {
        tok = Integer.parseInt(val);
      } else {
        Log.v(PNBrowser.TAG, "ERROR: unknown place attribute: " + name + "=" + atts.getValue(i));
      }
    }
    //now create the node and add in the proxy
    NodeProxy proxy = nodeMap.get(id);
    if (proxy == null) {
      Log.v(PNBrowser.TAG, "ERROR: Place with id=" + id + " has no reference" );
      return;
    }
    Place place = new Place(top, n, l, t, r - l, b - t, 1.0);
    //add in tokens
    for (int i = 0; i < tok; i++) {
      place.addToken(false);
    }
    top.insertElement(place);
    proxy.node = place;
  }

  private void processtransAttributes(Attributes atts) {
    assert inTop;
    assert !(inNodes || inArcs);
    String id = null;
    int l = 0, r = 0, t = 0, b = 0;
    String n = null;
    for (int i = 0; i < atts.getLength(); i++) {
      //Log.v(PNBrowser.TAG, "trans attr: " + atts.getLocalName(i) + " = " + atts.getValue(i) + " : " + atts.getType(i));
      String name = atts.getLocalName(i).trim();
      String val = atts.getValue(i).trim();
      if (name.equals("id")) {
        id = val;
      } else if (name.equals("n")) {
        n = val;
      } else if (name.equals("l")) {
        l = Integer.parseInt(val);
      } else if (name.equals("r")) {
        r = Integer.parseInt(val);
      } else if (name.equals("t")) {
        t = Integer.parseInt(val);
      } else if (name.equals("b")) {
        b = Integer.parseInt(val);
      } else {
        Log.v(PNBrowser.TAG, "ERROR: unknown trans attribute: " + name + "=" + atts.getValue(i));
      }
    }
    //now create the node and add in the proxy
    NodeProxy proxy = nodeMap.get(id);
    if (proxy == null) {
      Log.v(PNBrowser.TAG, "ERROR: trans with id=" + id + " has no reference" );
      return;
    }
    Transition trans = new Transition(top, n, l, t, r - l, b - t, 1.0);
    top.insertElement(trans);
    proxy.node = trans;
  }

  private void processRefAttributes(Attributes atts) {
    assert inTop;
    assert !(inNodes && inArcs);
    for (int i = 0; i < atts.getLength(); i++) {
      //Log.v(PNBrowser.TAG, "ref attr: " + atts.getLocalName(i) + " = " + atts.getValue(i) + " : " + atts.getType(i));
      String name = atts.getLocalName(i).trim();
      if (name.equals("id")) {
        String val = atts.getValue(i);
        if (inNodes) {
          if (nodeMap.containsKey(val)) {
            Log.v(PNBrowser.TAG, "ERROR: Node reference with id=" + atts.getValue(i) + " multiply defined" );
          }
          nodeMap.put(val, new NodeProxy(val));
        } if (inArcs) {
          if (arcMap.containsKey(val)) {
            Log.v(PNBrowser.TAG, "ERROR: Arc reference with id=" + atts.getValue(i) + " multiply defined" );
          }
          arcMap.put(val, new ArcProxy(val));
        }
      } else {
        Log.v(PNBrowser.TAG, "ERROR: unknown ref attribute: " + name + "=" + atts.getValue(i));
      }
    }
  }

  private void processTopAttributes(Attributes atts) {
    for (int i = 0; i < atts.getLength(); i++) {
      //Log.v(PNBrowser.TAG, "top attr: " + atts.getLocalName(i) + " = " + atts.getValue(i) + " : " + atts.getType(i));
      String name = atts.getLocalName(i).trim();
      if (name.equals("id")) {
        String val = atts.getValue(i);
        top  = new RootNode();
        NodeProxy proxy = new NodeProxy(val);
        proxy.node  = top;
        nodeMap.put(val, proxy);
      } else {
        Log.v(PNBrowser.TAG, "ERROR: unknown top attribute: " + name + "=" + atts.getValue(i));
      }
    }
  }

  public void endElement(String uri, String name, String qName) throws SAXException {
    //Log.v(PNBrowser.TAG, "endElement: " + name + ", qName: " + qName);
    if (name.trim().equals("top")) {
      inTop = false;
    } else if (name.trim().equals("nodes")) {
      inNodes = false;
    } else if (name.trim().equals("arcs")) {
      inArcs = false;
    }
  }

  public void characters(char ch[], int start, int length) {
    String chars = (new String(ch).substring(start, start + length)).trim();
    if (chars.length() > 0) {
      //we don't really expect any characters
      Log.v(PNBrowser.TAG, "characters: " + chars);
    }
  }

  public RootNode parse() {
    try {
      SAXParserFactory spf = SAXParserFactory.newInstance();
      SAXParser sp = spf.newSAXParser();
      org.xml.sax.XMLReader xr = sp.getXMLReader();
      xr.setContentHandler(this);
      xr.parse(new InputSource(is));
    } catch (IOException e) {
      Log.e(PNBrowser.TAG, e.toString());
      // e.printStackTrace();
    } catch (SAXException e) {
      Log.e(PNBrowser.TAG, e.toString());
      // e.printStackTrace();
    } catch (ParserConfigurationException e) {
      Log.e(PNBrowser.TAG, e.toString());
      // e.printStackTrace();
    }
    // now create connectors
    ArcProxy[] values = new ArcProxy[arcMap.size()];
    arcMap.values().toArray(values);
    for (int i = 0; i < values.length; i++) {
      ArcProxy proxy = values[i];
      NodeProxy toProxy = nodeMap.get(proxy.toId);
      NodeProxy fromProxy = nodeMap.get(proxy.fromId);
      if (toProxy != null && fromProxy != null) {
        AbstractComponent to = toProxy.node;
        AbstractComponent from = fromProxy.node;
        if (to != null && from != null) {
          top.insertDeleteArc(from, to);
        }
      }
    }
    return top;
  }
  
  private class NodeProxy {

    public AbstractComponent node = null;
    String id;
    
    public NodeProxy(String id) {
      this.id = id;
    }
    
  }
  
  private class ArcProxy {
    
    public String name = null;
    public String fromId = null;
    public String toId = null;
    String id;
    
    public ArcProxy(String id) {
      this.id = id;
    }
    
  }

}
